package org.xdoc;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;

/**
 * XDoc
 * @author xdoc
 */
public class XDoc extends Container {
	/**
	 * XDoc服务器地址
	 */
	public static String SERVER_URL = "http://myxdoc.sohuapps.com"; 
	/**
	 * 版本号
	 */
	public static final String VERSION = "9.5.6";
    /**
     * XDoc
     */
    public XDoc() {
        super("xdoc");
        this.setAttribute("version", VERSION);
        this.add(this.meta);
        this.add(this.paper);
        this.add(this.front);
        this.add(this.back);
        this.add(this.body);
    }
    private Meta meta = new Meta();
    private Paper paper = new Paper();
    private Rect front = new Rect("front");
    private Rect back = new Rect("back");
    private Body body = new Body();
    /**
	 * 获取元数据
	 * @return
	 */
	public Meta getMeta() {
	    return meta;
	}
	/**
	 * 获取纸张
	 * @return
	 */
	public Paper getPaper() {
	    return paper;
	}
	/**
     * 获取背景
     * @return
     */
    public Rect getBack() {
        back.setWidth(this.paper.getWidth());
        back.setHeight(this.paper.getHeight());
        return back;
    }
    /**
	 * 获取前景
	 * @return
	 */
	public Rect getFront() {
	    front.setWidth(this.paper.getWidth());
	    front.setHeight(this.paper.getHeight());
	    return front;
	}
	/**
     * 获取内容
     * @return
     */
    public Body getBody() {
        return body;
    }
    /**
	 * 转换为W3C Document
	 * @return
	 * @throws Exception 
	 */
	public Document toDocument() {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();   
			DocumentBuilder builder = factory.newDocumentBuilder();   
			Document document = builder.newDocument();
			document.appendChild(this.toElement(document));
			return document;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * 转换为XML
	 * @throws Exception 
	 */
	public String toXml() {
		try {
			ByteArrayOutputStream bout = new ByteArrayOutputStream();
			save(bout);
			return new String(bout.toByteArray(), "UTF-8");
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * 保存
	 * @param out
	 * @throws Exception
	 */
	public void save(OutputStream out) throws IOException {
		try {
			TransformerFactory tf = TransformerFactory.newInstance();   
			Transformer transformer = tf.newTransformer();
			DOMSource source = new DOMSource(toDocument());   
			transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");   
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");   
			StreamResult result = new StreamResult(out);   
			transformer.transform(source, result);
			out.flush();
		} catch (TransformerException e1) {
			throw new IOException(e1);
		}   
	}
	/**
	 * 转换
	 * @param file 文件
	 * @throws IOException
	 */
	public void to(File file) throws IOException {
		to(this.toXml(), file);
	}
	/**
	 * 转换
	 * @param out
	 * @param format
	 * @throws IOException
	 */
	public void to(OutputStream out, String format) throws IOException {
		if (format.equals("xdoc")) {
			this.save(out);
		} else {
			to(this.toXml(), out, format);
		}
	}
    /**
	 * 运行
	 * @param param
	 * @param out
	 * @param format
	 * @throws IOException
	 */
	public void run(Map<String, String> param, File file) throws IOException {
		run(this.toXml(), param, file);
	}
	/**
	 * 运行
	 * @param param
	 * @param out
	 * @param format
	 * @throws IOException
	 */
	public void run(Map<String, String> param, OutputStream out, String format) throws IOException {
		run(this.toXml(), param, out, format);
	}
	/**
	 * 转换为指定文件，文件格式自动识别
	 * @param xdocUrl
	 *        URL：文档URL地址，格式支持：xdoc、json、docx、epub、txt、rtf等，支持datauri协议，可传递二进制数据<br>
	 *        纯文本：以"text:"开头的文本<br>
	 *        JSON：符合XDOC-JSON规范的JSON文本<br>
	 *        XML：符合XDOC-XML规范的XML文本<br>
	 *        HTML：用html标签括起来的html文本，如：&lt;html&gt;&lt;h1&gt;Hello&lt;/h1&gt;&lt;/html&gt;
	 * @param file
	 * @throws IOException
	 */
	public static void to(String xdocUrl, File file) throws IOException {
		FileOutputStream fout = new FileOutputStream(file);
		to(xdocUrl, fout, getFormat(file.getName()));
		fout.flush();
		fout.close();
	}
	/**
	 * 以指定格式转换到流中
	 * @param xdocUrl
	 * @param out
	 * @param format
	 * @throws IOException
	 */
	public static void to(String xdocUrl, OutputStream out, String format) throws IOException {
		Map<String, String> param = new HashMap<String, String>();
		param.put("_func", "to");
		param.put("_url", xdocUrl);
		param.put("_format", format);
		invoke(param, out);
	}
	/**
	 * 运行xdoc，结果保存到文件中
	 * @param xdocUrl
	 * @param param
	 * @param file
	 * @throws IOException
	 */
	public static void run(String xdocUrl, Map<String, String> param, File file) throws IOException {
		FileOutputStream fout = new FileOutputStream(file);
		run(xdocUrl, param, fout, getFormat(file.getName()));
		fout.flush();
		fout.close();
	}
	/**
	 * 运行xdoc，结果以指定文档格式保存到流中
	 * @param xdocUrl
	 * @param param
	 * @param out
	 * @param format
	 * @throws IOException
	 */
	public static void run(String xdocUrl, Map<String, String> param, OutputStream out, String format) throws IOException {
		param.put("_func", "run");
		param.put("_url", xdocUrl);
		param.put("_format", format);
		invoke(param, out);
	}
	private static String getFormat(String url) {
	    String format = "xdoc";
	    int pos = url.lastIndexOf(".");
	    if (pos > 0) {
	        format = url.substring(pos + 1).toLowerCase();
	        if (format.equals("zip")) {
	            url = url.substring(0, pos);
	            pos = url.lastIndexOf(".");
	            if (pos > 0) {
	                format = url.substring(pos + 1).toLowerCase() + ".zip";
	            }
	        }
	    }
	    return format;
	}
	private static void invoke(Map<String, String> param, OutputStream out) throws IOException {
		String url = SERVER_URL + (SERVER_URL.endsWith("/") ? "xdoc" : "/xdoc");
		HttpURLConnection httpConn = (HttpURLConnection) new URL(url).openConnection();
		httpConn.setDoOutput(true);
		OutputStream reqOut = httpConn.getOutputStream();
        Iterator<String> it = param.keySet().iterator();
        String key;
        while (it.hasNext()) {
            key = it.next();
            reqOut.write(("&" + encode(key) + "=").getBytes());
            reqOut.write(encode(param.get(key)).getBytes());
        }
        reqOut.flush();
        reqOut.close();
        pipe(httpConn.getInputStream(), out);

	}
    private static String encode(String str) {
        try {
            return URLEncoder.encode(str, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            return str;
        }
    }
    private static void pipe(InputStream in, OutputStream out) throws IOException {
        int len;
        byte[] buf = new byte[4096];
        while (true) {
            len = in.read(buf);
            if (len > 0) {
                out.write(buf, 0, len);
            } else {
                break;
            }
        }
        out.flush();
    }

}